﻿// -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	SPWebExts.cs
//  Developer:	Igor Solomatov
//  Created:	12.11.2012
// 
// -------------------- iSharePoint -------------------- //

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;

namespace Smart.Common
{
	[Localizable(false)]
	[DebuggerStepThrough]
	public static class SPWebExts
	{
		/// <summary>
		/// Ensures user on web, checks whether login is user login
		/// </summary>
		public static SPUser SafeEnsureUser(this SPWeb web, string loginName)
		{
			if (!loginName.IsUsersLogin())
				return null;

			var sw = Stopwatch.StartNew();

			var user = web.EnsureUser(loginName);

			sw.Stop();

			if (sw.ElapsedMilliseconds > 100)
				H.LT("SafeEnsureUser: very slow user ensure for '{0}', {1}ms!", loginName, sw.ElapsedMilliseconds);

			return user;
		}

		/// <summary>
		/// Always returns root web
		/// </summary>
		public static SPWeb GetRootWeb(this SPWeb web)
		{
			return web.IsRootWeb ? web : web.Site.RootWeb;
		}

		/// <summary>
		/// Returns list from <paramref name="web"/> by web-relative or full <paramref name="url"/>
		/// </summary>
		public static SPList GetListByUrl(this SPWeb web, string url)
		{
			SPList list = null;

			if (web != null && !url.IsNullOrEmpty())
			{
				string fullUrl;

				switch (url.DetectUrlType())
				{
					case UrlType.None:
						return null;

					case UrlType.Abolute:
						fullUrl = url;
						break;

					case UrlType.ServerRelative:
						fullUrl = web.Site.MakeFullUrl(url);
						break;

					case UrlType.SiteRelative:
						fullUrl = SPUrlUtility.CombineUrl(web.Url, url);
						break;

					default:
						return null;
				}

				try
				{
					list = web.GetList(fullUrl);
				}
				catch (Exception)
				{
					H.LT("GetListByUrl (" + web.Title + ", " + url + ") failed!");
					list = null;
				}
			}

			return list;
		}

		/// <summary>
		/// Returns list from <paramref name="web"/> by <paramref name="listIdOrTitle"/>
		/// </summary>
		public static SPList GetListByName(this SPWeb web, string listIdOrTitle)
		{
			if (string.IsNullOrEmpty(listIdOrTitle)) return null;

			var listId = listIdOrTitle.SToGuid(Guid.Empty);

			SPList res = null;

			try
			{
				res = (listId != Guid.Empty ? web.Lists[listId] : web.Lists[listIdOrTitle]);
			}
			catch (Exception)
			{
				H.LT("GetListByName ({0}, {1}) failed!", web.Title, listIdOrTitle);
			}

			return res;
		}

		/// <summary>
		/// Returns list from <paramref name="web"/> by <paramref name="listId"/>
		/// </summary>
		public static SPList GetListById(this SPWeb web, Guid listId)
		{
			if (listId == Guid.Empty) return null;

			SPList res = null;

			try
			{
				res = web.Lists[listId];
			}
			catch (Exception)
			{
				H.LT("GetListByID ({0}, {1}) failed!", web.Title, listId);
			}

			return res;
		}

		/// <summary>
		/// Returns lib from <paramref name="web"/> by <paramref name="libIdOrTitle"/>
		/// </summary>
		public static SPDocumentLibrary GetLibByName(this SPWeb web, string libIdOrTitle)
		{
			var list = GetListByName(web, libIdOrTitle);

			return list as SPDocumentLibrary;
		}

		/// <summary>
		/// Returns lib from <paramref name="web"/> by <paramref name="libId"/>
		/// </summary>
		public static SPDocumentLibrary GetLibById(this SPWeb web, Guid libId)
		{
			var list = GetListById(web, libId);

			return list as SPDocumentLibrary;
		}

		/// <summary>
		/// Searches list by <paramref name="url"/>, url is not exact list url
		/// </summary>
		public static SPList FindListByUrl(this SPWeb web, string url)
		{
			if (web != null && !url.IsNullOrEmpty())
			{
				string fullUrl;

				switch (url.DetectUrlType())
				{
					case UrlType.None:
						return null;

					case UrlType.Abolute:
						fullUrl = url.TrimEnd('/');
						break;

					case UrlType.ServerRelative:
						fullUrl = web.Site.MakeFullUrl(url.TrimEnd('/'));
						break;

					case UrlType.SiteRelative:
						fullUrl = SPUrlUtility.CombineUrl(web.Url, url.TrimEnd('/'));
						break;

					default:
						return null;
				}

				var minLen = fullUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase) + 3;

				while (true)
				{
					SPList list;

					try
					{
						list = web.GetList(fullUrl);
					}
					catch
					{
						list = null;
					}

					if (list != null)
						return list;

					var idx = fullUrl.LastIndexOf('/');

					if (idx >= minLen)
					{
						fullUrl = fullUrl.Substring(0, idx);
					}
					else
					{
						return null;
					}
				}
			}

			return null;
		}

		/// <summary>
		/// Searches folder by <paramref name="webRelativeFolderUrl"/>, url is not exact folder url
		/// </summary>
		public static SPFolder FindFolderByUrl(this SPWeb web, string webRelativeFolderUrl)
		{
			if (webRelativeFolderUrl.IsNullOrEmpty())
				return null;

			while (true)
			{
				var folder = web.GetFolder(webRelativeFolderUrl);

				if (folder != null && folder.Exists)
					return folder;

				var idx = webRelativeFolderUrl.TrimEnd('/').LastIndexOf('/');

				if (idx >= 0)
				{
					webRelativeFolderUrl = webRelativeFolderUrl.Substring(0, idx + 1);
				}
				else
				{
					return null;
				}
			}
		}

		/// <summary>
		/// Returns <paramref name="web"/>-relative <paramref name="url"/>
		/// </summary>
		public static string GetWebRelativeUrl(this SPWeb web, string url)
		{
			switch (url.DetectUrlType())
			{
				case UrlType.None:
					return url;

				case UrlType.Abolute:
					return url.TrimStart(web.Url.EnsureEndsWith("/"));

				case UrlType.ServerRelative:
					return url.TrimStart(web.ServerRelativeUrl.EnsureEndsWith("/"));

				case UrlType.SiteRelative:
					return url;

				default:
					return url;
			}
		}

		/// <summary>
		/// Deletes specified web and all of its children!
		/// </summary>
		public static bool SafeDeleteWithChildren(this SPWeb web)
		{
			if (web == null || !web.Exists) return false;

			var subWebs = web.Webs.ToArray();

			for (int i = 0, iMax = subWebs.Length; i < iMax; i++)
			{
				try
				{
					using (var subWeb = subWebs[i])
						if (!subWeb.SafeDeleteWithChildren())
							return false;
				}
				catch (Exception x)
				{
					H.LX("SPWebExts.SafeDeleteWithChildren", "SafeDeleteWithChildren subWeb:" + (subWebs[i] != null ? subWebs[i].ServerRelativeUrl : "null"), x);
					return false;
				}
			}

			try
			{
				web.Delete();
				return true;
			}
			catch (Exception x)
			{
				H.LX("SPWebExts.SafeDeleteWithChildren", "SafeDeleteWithChildren web:" + web.ServerRelativeUrl, x);
				return false;
			}
		}

		/// <summary>
		/// Run <paramref name="code"/> under elevated mode for <paramref name="web"/>
		/// </summary>
		/// <exception cref="TargetInvocationException">Exception rethrowed</exception>
		public static bool RunElevated(this SPWeb web, SPSecurity.CodeToRunElevated code)
		{
			var res = true;

			if (SPH.UnderElevatedMode())
			{
				// Reuse elevated mode, instead of entering it twice

				H.LT("RunElevated: Reusing elevated mode");

				SPH.EnterElevatedMode(web);

				try
				{
					code();
				}
				catch (Exception x)
				{
					//res = false;
					H.LX("RunElevated.r", x);
					throw new TargetInvocationException(x);
				}
				finally
				{
					SPH.LeaveElevatedMode();
				}
			}
			else
			{
				H.LT("RunElevated: Using new elevated mode");

				SPH.ValidateFormDigest();

				SPSecurity.RunWithElevatedPrivileges(
					delegate
					{
						SPH.EnterElevatedMode(web);

						try
						{
							code();
						}
						catch (Exception x)
						{
							res = false;
							H.LX("RunElevated.n", x);
							throw new TargetInvocationException(x);
						}
						finally
						{
							SPH.LeaveElevatedMode();
						}
					});
			}

			return res;
		}

		/// <summary>
		/// Executes <paramref name="method"/> for each list in <paramref name="web"/>
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="method"/> is null</exception>
		public static void ForEachList(this SPWeb web, Action<SPList> method)
		{
			if (web == null) return;
			if (method == null) throw new ArgumentNullException("method");

			foreach (SPList list in web.Lists)
				method(list);
		}

		/// <summary>
		/// Executes <paramref name="method"/> for each list in <paramref name="web"/>, all lists are saved in temporary collection
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="method"/> is null</exception>
		public static void SafeForEachList(this SPWeb web, Action<SPList> method)
		{
			if (web == null) return;
			if (method == null) throw new ArgumentNullException("method");

			var lists = web.Lists.Cast<SPList>().ToArray();

			for (int i = 0, iCnt = lists.Length; i < iCnt; i++)
				method(lists[i]);
		}

		/// <summary>
		/// Executes <paramref name="method"/> for each <paramref name="web"/> workflow association
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="method"/> is null</exception>
		public static void ForEachAssociation(this SPWeb web, Action<SPWorkflowAssociation> method)
		{
			if (web == null) return;
			if (method == null) throw new ArgumentNullException("method");

			foreach (SPWorkflowAssociation association in web.WorkflowAssociations)
				method(association);
		}

		/// <summary>
		/// Executes <paramref name="method"/> for each <paramref name="web"/> workflow association, all associations are saved in temporary collection
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="method"/> is null</exception>
		public static void SafeForEachAssociation(this SPWeb web, Action<SPWorkflowAssociation> method)
		{
			if (web == null) return;
			if (method == null) throw new ArgumentNullException("method");

			var associations = web.WorkflowAssociations.Cast<SPWorkflowAssociation>().ToArray();

			for (int i = 0, iCnt = associations.Length; i < iCnt; i++)
				method(associations[i]);
		}

		/// <summary>
		/// Set all templates for <paramref name="contentTypeId"/>
		/// </summary>
		public static void BindFormTemplates(this SPWeb web, bool searchInParentWeb, SPContentTypeId contentTypeId, string newFormTemplateName, string editFormTemplateName, string displayFormTemplateName)
		{
			var ct = web.ContentTypes[contentTypeId];

			if (ct != null)
			{
				if (ct.NewFormTemplateName == newFormTemplateName && ct.EditFormTemplateName == editFormTemplateName && ct.DisplayFormTemplateName == displayFormTemplateName)
					return;

				ct.NewFormTemplateName = newFormTemplateName;
				ct.EditFormTemplateName = editFormTemplateName;
				ct.DisplayFormTemplateName = displayFormTemplateName;

				ct.Update(true, false);
			}
			else if (searchInParentWeb)
			{
				var parentWeb = web.ParentWeb;

				while (parentWeb != null && ct == null)
				{
					ct = parentWeb.ContentTypes[contentTypeId];

					if (ct == null)
					{
						parentWeb = parentWeb.ParentWeb;
					}
					else
					{
						if (ct.NewFormTemplateName == newFormTemplateName && ct.EditFormTemplateName == editFormTemplateName && ct.DisplayFormTemplateName == displayFormTemplateName)
							return;

						ct.NewFormTemplateName = newFormTemplateName;
						ct.EditFormTemplateName = editFormTemplateName;
						ct.DisplayFormTemplateName = displayFormTemplateName;

						ct.Update(true, false);
					}
				}
			}

			if (ct == null)
			{
				H.LT("BindFormTemplates.ContentTypeNotFoundException: " + contentTypeId);
			}
		}

		/// <summary>
		/// Bind Workflow to Content Type <paramref name="contentTypeId"/>
		/// </summary>
		public static void BindWorkflow(this SPWeb web, SPContentTypeId contentTypeId, string workflowName, string taskListTitle, string historyListTitle)
		{
			var siteContentType = web.ContentTypes[contentTypeId];

			SPWorkflowAssociation wfAssoc;

			// Get a template.
			var workflowTemplate = web.WorkflowTemplates.Cast<SPWorkflowTemplate>().FirstOrDefault(wft => wft.Name == workflowName);

			// Add the association to the content type or update it if it already exists.)
			if ((wfAssoc = siteContentType.WorkflowAssociations.GetAssociationByName(workflowName, web.Locale)) == null)
			{
				wfAssoc =
					SPWorkflowAssociation.CreateWebContentTypeAssociation(
						workflowTemplate,
						workflowName,
						taskListTitle,
						historyListTitle);

				siteContentType.WorkflowAssociations.Add(wfAssoc);
			}
			else
			{
				siteContentType.WorkflowAssociations.Update(wfAssoc);
			}

			// Propagate to children of this content type.
			siteContentType.UpdateWorkflowAssociationsOnChildren(
				false, // Do not generate full change list
				true, // Push down to derived content types
				true, // Push down to list content types
				false); // Do not throw exception if sealed or readonly  
		}

		/// <summary>
		/// Read whole file
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="web"/> is null</exception>
		/// <exception cref="ArgumentNullException"><paramref name="webRelativeFileUrl"/> is null</exception>
		public static byte[] ReadFile(this SPWeb web, string webRelativeFileUrl)
		{
			if (web == null) throw new ArgumentNullException("web");
			if (webRelativeFileUrl == null) throw new ArgumentNullException("webRelativeFileUrl");

			try
			{
				return web.GetFile(webRelativeFileUrl).ReadFile();
			}
			catch (Exception x)
			{
				H.LX("ReadFile", x);
			}

			return null;
		}

		/// <summary>
		/// Reads whole file stream
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="web"/> is null</exception>
		/// <exception cref="ArgumentNullException"><paramref name="webRelativeFileUrl"/> is null</exception>
		public static Stream ReadFileStream(this SPWeb web, string webRelativeFileUrl)
		{
			if (web == null) throw new ArgumentNullException("web");
			if (webRelativeFileUrl == null) throw new ArgumentNullException("webRelativeFileUrl");

			try
			{
				return web.GetFile(webRelativeFileUrl).ReadFileStream();
			}
			catch (Exception x)
			{
				H.LX("ReadFileStream", x);
			}

			return null;
		}

		/// <summary>
		/// Saves whole file
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="web"/> is null</exception>
		/// <exception cref="ArgumentNullException"><paramref name="webRelativeFileUrl"/> is null</exception>
		public static bool SaveFile(this SPWeb web, string webRelativeFileUrl, byte[] data, bool overwrite)
		{
			if (web == null) throw new ArgumentNullException("web");
			if (webRelativeFileUrl == null) throw new ArgumentNullException("webRelativeFileUrl");

			try
			{
				return web.GetFile(webRelativeFileUrl).SaveFile(data, overwrite);
			}
			catch (Exception x)
			{
				H.LX("SaveFile.1", x);
			}

			return false;
		}

		/// <summary>
		/// Saves whole file stream
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="web"/> is null</exception>
		/// <exception cref="ArgumentNullException"><paramref name="webRelativeFileUrl"/> is null</exception>
		public static bool SaveFile(this SPWeb web, string webRelativeFileUrl, Stream data, bool overwrite)
		{
			if (web == null) throw new ArgumentNullException("web");
			if (webRelativeFileUrl == null) throw new ArgumentNullException("webRelativeFileUrl");

			try
			{
				return web.GetFile(webRelativeFileUrl).SaveFile(data, overwrite);
			}
			catch (Exception x)
			{
				H.LX("SaveFile.2", x);
			}

			return false;
		}
	}
}